/**
 * ConcreteView.java
 * ************************** 
 * @date Mar 17, 2011
 * @author Michele Tamburini
 * 
 * **************************
 */
package gossipServices.basic.view;

import gossipServices.basic.nodeDescriptors.ConcreteNodeDescriptor;
import gossipServices.basic.nodeDescriptors.NodeDescriptor;
import gossipServices.basic.nodeDescriptors.comparators.AgeInverseSorterer;
import gossipServices.basic.nodeDescriptors.comparators.AgeSorterer;
import gossipServices.basic.nodeDescriptors.comparators.NodeDescriptorComparator;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

/**
 * Description: 
 * This is an implementation of the PartialView.
 * It uses a Vector of ConcreteNodeDescriptors to keep the NodeDescriptors.
 * 
 */
public class ConcretePartialView implements PartialView {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	protected Vector<ConcreteNodeDescriptor> descriptors = new Vector<ConcreteNodeDescriptor>(); 

	//FOR DEBUG ONLY!!
	public final Vector<ConcreteNodeDescriptor> getDescriptors() {
		return descriptors;
	}
	
	/**
	 * It builds a new ConcretePartialView by initializing the 
	 * Vector with a fixed size.
	 * @param size : int
	 */
	public ConcretePartialView(int size) {
		descriptors.ensureCapacity(2 * size);
//		resizeDescriptors();
	}
	
	/**
	 * This is probably the easier way to build a PartialView
	 * by providing a list of ConcreteNodeDescriptors.
	 * @param descriptorsList
	 */
	public ConcretePartialView(List<NodeDescriptor> descriptorsList){
		for(NodeDescriptor n : descriptorsList){
			descriptors.add((ConcreteNodeDescriptor) n);
		}
//		descriptors.addAll(descriptorsList);
		resizeDescriptors();
	}
	
	/**
	 * @param adding
	 */
	public ConcretePartialView(Vector<NodeDescriptor> adding) {
		for(NodeDescriptor n : adding){
			descriptors.add((ConcreteNodeDescriptor)n);
		}
		resizeDescriptors();
	}

	private void resizeDescriptors(){
		descriptors.trimToSize();
	}
	
	public boolean addNodeDescriptor(ConcreteNodeDescriptor node){
		return descriptors.add(node);
	}

	@Override
	public boolean append(PartialView v) {
//		Vector<ConcreteNodeDescriptor> vec = ((ConcretePartialView)v).getDescriptors();
		for(int i=0; i<v.size(); i++){
//			descriptors.add(new ConcreteNodeDescriptor(v.getAt(i)));
			descriptors.add((ConcreteNodeDescriptor) v.getAt(i));
		}
		return true;
	}

	@Override
	public PartialView head(int elements) {
		Vector<NodeDescriptor> tempVec = new Vector<NodeDescriptor>();
		if(size() > 0){
			for(int i=0; i<elements && i < size(); i++){
				tempVec.add(new ConcreteNodeDescriptor(descriptors.get(i)));
			}
			tempVec.trimToSize();
		}
		return (PartialView) new ConcretePartialView(tempVec);
	}

	@Override
	public boolean increaseAge() {
		for(Iterator<ConcreteNodeDescriptor> it = descriptors.iterator(); 
		it.hasNext(); ){
			it.next().increaseAge();
		}
		return true;
	}

	@Override
	public boolean permute(int H) {
		boolean ret = true;
		Vector<ConcreteNodeDescriptor> tempVec = (Vector<ConcreteNodeDescriptor>) descriptors.clone();
//		Comparator<ConcreteNodeDescriptor> reverser = Collections.reverseOrder(); 
		Collections.sort(tempVec, new AgeInverseSorterer());
		for(int i=H-1; i>=0; i--){
			ConcreteNodeDescriptor node = tempVec.get(i);
			ret &= descriptors.remove(node);
			ret &= descriptors.add(node); //we re-insert (append to the end) the element just removed
		}
		tempVec.clear();
//		orderDescriptors();
		return ret;
	}

	 /**
	  * we want to build a new View which contains no duplicate
	  * respect to the current one. A temporary new 
	  * Vector<NodeDescriptor> is used to this purpose.
	  */
	@Override
	public boolean removeDuplicates() {
		Vector<ConcreteNodeDescriptor> newDescriptors = new Vector<ConcreteNodeDescriptor>();
		for(Iterator<ConcreteNodeDescriptor> it = descriptors.iterator();
				it.hasNext();){
			ConcreteNodeDescriptor node = it.next();
			
			//if the new view just contains the target node we have to 
			//control which is the freshest, keep it and discard the other.
			if( !(newDescriptors.contains(node)) ){
				newDescriptors.add(node);
			}else{
				int duplicate = newDescriptors.indexOf(node);
				if( node.isFresh(newDescriptors.get(duplicate)) ){
					newDescriptors.remove(duplicate);
					newDescriptors.add(node);
				}
			}
		}
		descriptors.clear();
		descriptors = newDescriptors;
		return true;
	}

	@Override
	public boolean removeHead(int elements) {
		for(int i=0; i<elements; i++){
			descriptors.remove(0);
		}
		return true;
	}

	@Override
	public boolean removeOldItems(int elements, int c) {
		if((elements > 0) && (size() - elements >= c) ){
			Vector<ConcreteNodeDescriptor> tempVec = new Vector<ConcreteNodeDescriptor>();
			for(ConcreteNodeDescriptor node : descriptors)
				tempVec.add(new ConcreteNodeDescriptor(node.getNodeName(),node.getAge()));
//			Comparator<ConcreteNodeDescriptor> revertedSort = Collections.reverseOrder();
			Collections.sort(tempVec, new AgeInverseSorterer());

			for(int i=0 ; i<elements; i++){
				descriptors.remove(tempVec.get(i));
			}
		}
		return true;
	}

	@Override
	public boolean select(int c, int H, int S, 
			PartialView bufferp, NodeDescriptor myDescriptor) {
		boolean ret = true;
		ret &= this.append(bufferp);
		ret &= this.removeDuplicates();
		ret &= this.removeNodeDescriptor(c,myDescriptor);
		ret &= this.removeOldItems(Math.min(H, (size() - S)), c );
		ret &= this.removeHead(Math.min(S, (size() - c)) );
		ret &= this.removeAtRandom(size()-c);		
		return ret;
	}

	/**
	 * @param myDescriptor
	 */
	public boolean removeNodeDescriptor(int c, NodeDescriptor myDescriptor) {
		boolean ret = false;
		/*
		 * We have to check if the size is greater that the minimal
		 * view size that's "c".
		 * If not than we have to keep the local node descriptor 
		 * in order to maintain a view of size "c".
		 */
		if(descriptors.size() >= c + 1){
			ret = descriptors.remove(myDescriptor);
		}
		 return ret;
	}

	@Override
	public ConcreteNodeDescriptor selectPeer(PEER_SELECTION_ENUM selection) {
		ConcreteNodeDescriptor ret = null;
		switch(selection){
		case rand:
			ret = selectRandomPeer();
			break;
		case tail:
			ret = selectTailPeer();
			break;
		}
		return ret;
	}
	
	private ConcreteNodeDescriptor selectRandomPeer(){
		int randNumber = randomInView();
		if(size() == 0)
			return null;
		return descriptors.get(randNumber);
	}
	
	private ConcreteNodeDescriptor selectTailPeer(){
		return descriptors.lastElement();
	}

	@Override
	public int size() {
		return descriptors.size();
	}
	
	private void orderDescriptors(){
		Collections.sort(descriptors, new AgeSorterer());		
	}

	@Override
	public boolean removeAtRandom(int elements) {
		int target;
		for(int i=0; i<elements; i++){
			target = randomInView();
			descriptors.remove(target);
		}		
		return true;
	}
	
	private int randomInView(){
		return new Float(Math.random() * (descriptors.size()-1)).intValue();
	}

	/* (non-Javadoc)
	 * @see gossipServices.utils.PartialViewInterface#insertNodeDescriptor(gossipServices.utils.NodeDescriptor)
	 */
	@Override
	public boolean insertNodeDescriptor(NodeDescriptor node) {
		addNodeDescriptor((ConcreteNodeDescriptor) node);
		return true;
	}
	
	public String toString(){
		String ret = new String("");
//		ret += super.toString()+" ";
		for(int i=0; i<size(); i++){
			ret += i +") "+descriptors.get(i)+"\n";
		}
		
		return ret;
		
	}

	/* (non-Javadoc)
	 * @see gossipServices.basic.PartialView#getAt(int)
	 */
	@Override
	public NodeDescriptor getAt(int index) {
		return descriptors.get(index);
	}

	/**
	 * The merge() function preserve the whole old view
	 * by adding only the noew NodeDescriptor(s).
	 */
	@Override
	public boolean merge(PartialView buffer) {
		boolean ret = true;
		NodeDescriptor node;
		for(int i=0; i<buffer.size(); i++){
			node = buffer.getAt(i);
			if(!descriptors.contains(node)){
				ret &= descriptors.add(new ConcreteNodeDescriptor(node));
			}
		}
		return ret;
	}

	@Override
	public void applyComparator(NodeDescriptorComparator comparator) {
		Collections.sort(descriptors, comparator);
	}

	@Override
	public boolean remove(NodeDescriptor node) {
		if(descriptors != null && descriptors.size() > 0)
			return descriptors.remove(node);
		return false;
	}

	@Override
	public NodeDescriptor find(String nodeName) {
		int index;
		NodeDescriptor node = new ConcreteNodeDescriptor(nodeName);
		if(!descriptors.isEmpty() && 
				(index = descriptors.indexOf(node)) != -1)
			return descriptors.get( index );
		return null;
	}

}
